package tenglang.zuul.zuul.filter;

import auth.server.inter.AuthConstants;
import auth.server.inter.pojo.SysMenu;
import auth.server.inter.security.LoginUser;
import auth.server.inter.security.VistorRecord;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.util.Asserts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import tenglang.common.jackson.BeanUtils;
import tenglang.common.redis.RedisCache;
import tenglang.common.servlet.BodyReaderHttpServletRequestWrapper;
import tenglang.common.servlet.HttpHelper;
import tenglang.zuul.config.ZuulFilterProperties;
import tenglang.zuul.zuul.NewZuulRouteLocator;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.sql.Timestamp;
import java.util.*;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

@Component
public class AuthFilter extends ZuulFilter {

    @Autowired
    private ZuulFilterProperties zuulFilterProperties;

    @Autowired
    private ConfigService configService;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    @Qualifier("resourceJwtTokenServices")
    private ResourceServerTokenServices resourceJwtTokenServices;

    @Autowired
    NewZuulRouteLocator routeLocator;

    private static Map<String,ZuulFilterProperties> zuulFilterMap=new HashMap();

    //各系统可以匿名访问的资源
    private static Map<String, List<RequestMatcher>>  zuulRequestMatcherMap=new HashMap<>();

    //各系统登录用户就可以访问的资源
    private static Map<String, List<RequestMatcher>>  zuulAuthRequestMatcherMap=new HashMap<>();


    private static Map<String,List<RequestMatcher>> zuulNoRecordRequestMatcherMap=new HashMap<>();


    //先从 cookie 中取 token，cookie 中取失败再从 header 中取，两重校验
    //通过工具类从 Cookie 中取出 token
    RequestHeaderRequestMatcher tokenRequestHeaderRequestMatcher=new RequestHeaderRequestMatcher("Authorization");

    //判断是否为ajax请求
    RequestHeaderRequestMatcher ajaxRequestHeaderMatcher=new RequestHeaderRequestMatcher("X-Requested-With");


    RequestMatcher tokenRequestParamMatcher=new RequestMatcher(){
        /**
         * Decides whether the rule implemented by the strategy matches the supplied request.
         *
         * @param request the request to check for a match
         * @return true if the request matches, false otherwise
         */
        @Override
        public boolean matches(HttpServletRequest request) {
            String tokenValue=request.getParameter("accessToken");
            return StringUtils.isNotBlank(tokenValue);
        }
     };

    private static final String acceptHeaderName="Accept";

    private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";

    private static final Set<String> BODY_READ_CONTENT_TYPE=new HashSet<>(Arrays.asList("application/json","application/xml"));

    //判断响应类型
    RequestHeaderRequestMatcher acceptRequestRequestMatcher=new RequestHeaderRequestMatcher("Accept");


    private static Logger logger= LoggerFactory.getLogger(AuthFilter.class);

    /**
     * to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
     * "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
     * We also support a "static" type for static responses see  StaticResponseFilter.
     * Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
     *
     * @return A String representing that type
     */
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    /**
     * filterOrder() must also be defined for a filter. Filters may have the same  filterOrder if precedence is not
     * important for a filter. filterOrders do not need to be sequential.
     *
     * @return the int order of a filter
     */
    @Override
    public int filterOrder(){
        return PRE_DECORATION_FILTER_ORDER-1;
    }

    /**
     * a "true" return from this method means that the run() method should be invoked
     *
     * @return true if the run() method should be invoked. false will not invoke the run() method
     */
    @Override
    public boolean shouldFilter() {

        //获取当前请求上下文，在这个上下文基础上可以做很多事情了。具体自己查看API。
        RequestContext requestContext = RequestContext.getCurrentContext();
        //获取原始Htpp请求，有这个对象也可以做很多事情了。自己发挥吧。
        HttpServletRequest request = requestContext.getRequest();
        String contenxt=request.getSession().getServletContext().getContextPath();
        String url=request.getRequestURI();
        System.out.println(url);
        url=url.substring(contenxt.length());
        url=url.startsWith("/")?url.substring(1):url;
        //提取前缀
        String prefix=url.substring(0,url.indexOf("/"));

        boolean check=true;
        if(zuulRequestMatcherMap.containsKey(prefix)){
            List<RequestMatcher> requestMatchers=zuulRequestMatcherMap.get(prefix);
            for (RequestMatcher requestMatcher:requestMatchers){
                 if(requestMatcher.matches(request)){
                     check=false;
                 }
            }
        }
        boolean isRecordRequest=true;
        if(zuulNoRecordRequestMatcherMap.containsKey(prefix)){
            List<RequestMatcher> requestMatchers=zuulNoRecordRequestMatcherMap.get(prefix);
            for (RequestMatcher requestMatcher:requestMatchers){
                 if(requestMatcher.matches(request)){
                     isRecordRequest=false;
                 }
            }
        }
        requestContext.set(AuthConstants.USER_ACCESS_LOGS_ENABLE,isRecordRequest);
        if(check && isRecordRequest){
            long nextId=Increatement.getNext();
            boolean isBodyContent=false;
            if(request.getMethod().equalsIgnoreCase("post") && (StringUtils.isBlank(request.getContentType()) || !request.getContentType().contains(FORM_CONTENT_TYPE) )){
                if(StringUtils.isBlank(request.getContentType())){
                    isBodyContent=true;
                }else{
                    for (String contentType:BODY_READ_CONTENT_TYPE){
                        if(request.getContentType().contains(contentType)){
                            isBodyContent=true;
                        }
                    }
                }
            }
            if(isBodyContent){
                try {
                    BodyReaderHttpServletRequestWrapper requestWrapper=new BodyReaderHttpServletRequestWrapper(request);
                    String body = HttpHelper.getBodyString(requestWrapper);
                    VistorRecord vistorRecord=VistorRecord.createByRequest(nextId,body,request);
                    requestContext.set(AuthConstants.USER_ACCESS_LOGS,vistorRecord);
                    requestContext.setRequest(requestWrapper);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else{
                VistorRecord vistorRecord=VistorRecord.createByRequest(nextId,null,request);
                requestContext.set(AuthConstants.USER_ACCESS_LOGS,vistorRecord);
            }
        }
        return check;
    }

    /**
     * if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter
     *
     * @return Some arbitrary artifact may be returned. Current implementation ignores it.
     * @throws ZuulException if an error occurs during execution.
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        try {
            if(tokenRequestHeaderRequestMatcher.matches(request) || tokenRequestParamMatcher.matches(request)) {
                String tokenValue = getToken(request);
                tokenValue = StringUtils.isBlank(tokenValue) ? request.getParameter(zuulFilterProperties.getRequestTokenName()) : tokenValue;
                OAuth2Authentication oAuth2Authentication = resourceJwtTokenServices.loadAuthentication(tokenValue);
                String systemName = getSystemName(request);
                String resourcePath = getResourcePath(request);
                List<RequestMatcher> zuulAuthRequestMatchers = zuulAuthRequestMatcherMap.get(systemName);
                LoginUser loginUser = redisCache.getCacheObject(AuthConstants.LOGIN_TOKEN_KEY + (String) oAuth2Authentication.getPrincipal());
                Assert.notNull(loginUser,"未知的登录用户或用户已强制退出！");
                if(requestContext.containsKey(AuthConstants.USER_ACCESS_LOGS)){
                    VistorRecord vistorRecord=(VistorRecord)requestContext.get(AuthConstants.USER_ACCESS_LOGS);
                    vistorRecord.setAuth(true);
                    vistorRecord.setUserName(loginUser.getUsername());
                    requestContext.set(AuthConstants.USER_ACCESS_LOGS,vistorRecord);
                }
                for (RequestMatcher requestMatcher : zuulAuthRequestMatchers) {//判断资源是否可以匿名访问,如果可以则直接放行
                    if (requestMatcher.matches(request)) {
                        dispatcherSuccess();
                        return null;
                    }
                }
                List<SysMenu> allMenus = redisCache.getCacheList(AuthConstants.SECURITY_ALL_MENUS_BYURI);
                Set<String> permissions = loginUser.getPermissions();
                SysMenu currentMenu = null;
                for (SysMenu allMenu : allMenus){
                    if (menusCompare(allMenu,systemName,request)) {
                        currentMenu = allMenu;
                        break;
                    }
                }
                String url=request.getRequestURI();
                Assert.notNull(currentMenu,"位置位置的数据访问,请为:"+url+",配置数据权限");
                if (currentMenu != null) {//如果未找到相应的资源则直接403
                    boolean permissionCheck = permissions.contains(AuthConstants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(currentMenu.getPerms()));
                    if (permissionCheck) {
                        dispatcherSuccess();
                        return null;
                    }
                }
            }
            dispatcherFail(null);
        }catch (Exception e){
            dispatcherFail(e);
        }
        return null;
    }

    @PostConstruct
    public void init(){
        String configStr=null;
        try {
            configStr=configService.getConfig(zuulFilterProperties.getAppsDataId(),zuulFilterProperties.getAppsGroup(),360000);
            update(configStr,zuulFilterProperties.getAppsDataId(),zuulFilterProperties.getAppsGroup());
        } catch (NacosException e) {
            e.printStackTrace();
        }
    }


    //成功转发
    private void dispatcherSuccess(){
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        if(requestContext.containsKey(AuthConstants.USER_ACCESS_LOGS)){
            VistorRecord vistorRecord=(VistorRecord)requestContext.get(AuthConstants.USER_ACCESS_LOGS);
            vistorRecord.setAuth(true);
            vistorRecord.setUpdateTime(new Timestamp(System.currentTimeMillis()));
            requestContext.set(AuthConstants.USER_ACCESS_LOGS,vistorRecord);
        }
        requestContext.setSendZuulResponse(true);
        requestContext.setResponseStatusCode(200);
    }

    private boolean menusCompare(SysMenu sysMenu,String systemFlag,HttpServletRequest request){
        if(StringUtils.isNotBlank(sysMenu.getDataComponent())){
            Set<String> urls=new HashSet<>();
            if(sysMenu.getDataComponent().indexOf(",")>-1){
                urls.addAll(Arrays.asList(sysMenu.getDataComponent().split(",")));
            }else{
                urls.add(sysMenu.getDataComponent());
            }
            Set<AntPathRequestMatcher> antPathRequestMatcherSet=new HashSet<>();
            urls.forEach(s->{
                antPathRequestMatcherSet.add(new AntPathRequestMatcher("/"+systemFlag+s));
            });
            boolean check=false;
            for(AntPathRequestMatcher antPathRequestMatcher:antPathRequestMatcherSet){
                   if(antPathRequestMatcher.matches(request)){
                       check=true;
                       break;
                   }
            }
            return check;
        }
        return false;
    }

   //失败转发
    private void dispatcherFail(Exception e){
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        HttpServletResponse response = requestContext.getResponse();
//        if(ajaxRequestHeaderMatcher.matches(request)){//响应ajax请求
            if(acceptRequestRequestMatcher.matches(request)){
                response.setHeader("Content-Type", request.getHeader(acceptHeaderName));
            }else{
                response.setHeader("Content-Type", "text/html");
            }
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(403);
            Map<String,String> responseMap=new HashMap<String,String>();
            responseMap.put("errorcode", "403");
            responseMap.put("errormsg", e!=null?e.getMessage():"请求被拦截,您无权访问改模块，请联系管理员!");
            requestContext.setResponseBody(JSON.toJSONString(responseMap));

        if(requestContext.containsKey(AuthConstants.USER_ACCESS_LOGS)){
            VistorRecord vistorRecord=(VistorRecord)requestContext.get(AuthConstants.USER_ACCESS_LOGS);
            vistorRecord.setAuth(false);
            vistorRecord.setUpdateTime(new Timestamp(System.currentTimeMillis()));
            requestContext.set(AuthConstants.USER_ACCESS_LOGS,vistorRecord);
        }
//        }else{
//            requestContext.setSendZuulResponse(false);
//            requestContext.setResponseStatusCode(403);
//            try {
//                response.sendRedirect(zuulFilterProperties.getPage403Url());
//            } catch (IOException eio) {
//                logger.error("无权访问转发失败!pageUrl:{}",zuulFilterProperties.getPage403Url(),eio);
//            }
//        }
    }

    /**
     * 自动更新权限信息
     * @param configStr
     * @param dataId
     * @param groupId
     */
    public synchronized static void update(String configStr,String dataId,String groupId){
        try {
            JsonNode configTree=BeanUtils.readTreeFromContent(configStr);
            ObjectNode configTreeObj=(ObjectNode)configTree;
            Iterator<Map.Entry<String, JsonNode>> configTreeIterator=configTreeObj.fields();
            while(configTreeIterator.hasNext()){
                Map.Entry<String, JsonNode> configTreeEntry=configTreeIterator.next();
                ZuulFilterProperties zuulFilterProperties=BeanUtils.getObjectMapper().readValue(configTreeEntry.getValue().toString(),ZuulFilterProperties.class);
                zuulFilterMap.put(configTreeEntry.getKey(),zuulFilterProperties);
                List<String> anonymous=zuulFilterProperties.getAnonymous();
                List<RequestMatcher>  anonymousRequestMatchers=new ArrayList<RequestMatcher>();
                for(String anonymou:anonymous){
                     if(anonymou.indexOf(":")>-1){
                         anonymousRequestMatchers.add(new AntPathRequestMatcher("/"+configTreeEntry.getKey()+anonymou.split(":",2)[0],anonymou.split(":",2)[1]));
                     }else{
                         anonymousRequestMatchers.add(new AntPathRequestMatcher("/"+configTreeEntry.getKey()+anonymou));
                     }
                }
                zuulRequestMatcherMap.put(configTreeEntry.getKey(),anonymousRequestMatchers);

                List<String> auths=zuulFilterProperties.getAuths();
                List<RequestMatcher>  authRequestMatchers=new ArrayList<RequestMatcher>();
                for(String auth:auths){
                    if(auth.indexOf(":")>-1){
                        authRequestMatchers.add(new AntPathRequestMatcher("/"+configTreeEntry.getKey()+auth.split(":",2)[0],auth.split(":",2)[1]));
                    }else{
                        authRequestMatchers.add(new AntPathRequestMatcher("/"+configTreeEntry.getKey()+auth));
                    }
                }
                zuulAuthRequestMatcherMap.put(configTreeEntry.getKey(),authRequestMatchers);

                List<String> noRecords=zuulFilterProperties.getNoRecords();
                List<RequestMatcher>  noRecordsRequestMatchers=new ArrayList<RequestMatcher>();
                for(String record:noRecords){
                    if(record.indexOf(":")>-1){
                        noRecordsRequestMatchers.add(new AntPathRequestMatcher("/"+configTreeEntry.getKey()+record.split(":",2)[0],record.split(":",2)[1]));
                    }else{
                        noRecordsRequestMatchers.add(new AntPathRequestMatcher("/"+configTreeEntry.getKey()+record));
                    }
                }
                zuulNoRecordRequestMatcherMap.put(configTreeEntry.getKey(),noRecordsRequestMatchers);
            }
        }catch (Exception readErr){
            logger.error("paras config from nacos fail dataId:{},group:{},content:{}",dataId,groupId,configStr,readErr);
        }
    }

    /**
     * 自动提取token
     * @param request
     * @return
     */
    private String getToken(HttpServletRequest request)
    {
        String token = request.getHeader(zuulFilterProperties.getHeaderName());
        if (StringUtils.isNotEmpty(token) && token.startsWith(zuulFilterProperties.getTokenPrefix()))
        {
            token = token.replace(zuulFilterProperties.getTokenPrefix(), "");
        }
        return token;
    }

    private String getSystemName(HttpServletRequest request){
        String contenxt=request.getSession().getServletContext().getContextPath();
        String url=request.getRequestURI();
        System.out.println(url);
        url=url.substring(contenxt.length());
        url=url.startsWith("/")?url.substring(1):url;
        String prefix=url.substring(0,url.indexOf("/"));
        return prefix;
    }

    private String getResourcePath(HttpServletRequest request){
        String contenxt=request.getSession().getServletContext().getContextPath();
        String url=request.getRequestURI();
        url=url.substring(contenxt.length());
        url=url.startsWith("/")?url.substring(1):url;
        String prefix=url.substring(0,url.indexOf("/"));

        url=url.substring(prefix.length());
        url=url.startsWith("/")?url.substring(1):url;
        return url;
    }

    public static void main(String[] args) {
//        AntPathMatcher matcher = new AntPathMatcher();
//        matcher.setTrimTokens(false);
//        matcher.setCaseSensitive(false);
//        boolean bb=matcher.match("/system/dept/roleDeptTreeselect/**","/system/dept/roleDeptTreeselect/1");
//        System.out.println(bb);

        Long time=System.currentTimeMillis()*10+1;
        System.out.println(time);

        Set<Long> sets=new HashSet<>();
        int a=2000;
        while(a>0){
            sets.add(Increatement.getNext());
            a--;
        }
        System.out.println(sets.size());

    }

    static class Increatement{
         private static Integer currentTimeMillisNum=99;

         public static synchronized Long getNext(){
               if(currentTimeMillisNum>=1){
                   return System.currentTimeMillis()*100+(currentTimeMillisNum--);
               }else{
                   try {
                       Thread.sleep(1L);
                   } catch (InterruptedException e) {
                        Long currentMillis=System.currentTimeMillis();
                        Long lastMillis=0L;
                        while(lastMillis<currentMillis){
                            lastMillis=System.currentTimeMillis();
                        }
                   }
                   currentTimeMillisNum=99;
                   return System.currentTimeMillis()*100+(currentTimeMillisNum--);
               }
         }
    }
}



